﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using System.IO;
using System.IO.Compression;
//using ICSharpCode.SharpZipLib.Tar;
using Newtonsoft.Json;

namespace BestClient
{
    public class MessageConverter {
    private const string TAG = "MessageConverter";
    private const string DEVICE_KEY = "12345678";
    private const string SNAPSHOT = "snapshot";
    private const string START = "start";
    private const string END = "end";
    private const string OFFSET = "offset";
    private const string SIZE = "size";
    private const string ANDROID_KEY = "android";
    private const string EN_KEY = "EN";
    private const string COMPANY_ID = "cmpId";
    private const string ZONE = "zone";
    private const string DATA_TAG = "dataTag";
    private const string PROTOCOL = "prot";
    private const string VER = "ver";
    private const string DID = "did";
    private const string MFR = "mfr";
    private const string DEVICE_TYPE = "deviceType";
    private const string NO1BOSS = "faceCamera";
    private const string ZKTECO = "zkteco";
    private const string TOKEN = "token";
    private const string USER_SYNC_ON = "userSyncOn";
    private const string GROUP_SYN_CON = "groupSyncOn";
    private const string FW_VERSION = "FWVersion";
    private const string DELAY = "delay";
    private const string PARAM_LIST = "paramList";
    private const string DATE = "date";
    private const string LANGUAGE = "language";
    private const string ALGORITHM_VERSION = "algorithmVersion";
    private const string SYSTEM_VERSION = "systemVersion";
    private const string FIRMWARE_VERSION = "firmwareVersion";
    private const string SERIAL_NUMBER = "serialNumber";
    private const string P2P_ID = "p2pID";
    private const string COMMUN_PROTOCOL_VERSION = "communProtocolVersion";
    private const string IP_ADDRESS = "ipAddress";
    private const string DATA_FORMAT = "datafmt";
    private static string clientToken = null;

    /**
     * Joining together the message header
     *
     * @param sid service id
     * @return standard data format
     */
    protected static BestData buildHead(string sid, Context context, string sys) {
        BestData request = new BestData();
        Random random = new Random();
        request.sid = sid;
        request.mid = random.Next().ToString();
        request.sys = sys;
        request.platform = ANDROID_KEY;
        request.lang = EN_KEY;
        request.sn = getDeviceSN();
        if (clientToken == null) {
            clientToken = (string) SettingsProvider.getInstance().get("token", "null");
        }
        request.token = clientToken;
        return request;
    }


    /**
     * Get device sn
     *
     * @return sn
     */
    public static string getDeviceSN() {
        return SettingsProvider.getInstance().getDeviceSN();
    }


    /**
     * Get device, sn, mids md5 encrypted string
     *
     * @param mid message ID:create an unique code, the length is 8
     * @return mid string
     */
    public static string generateDid(string mid)
    {
        return md5Enc(DEVICE_KEY + getDeviceSN() + mid);
    }

    /**
     * use Md5 to encrypt a string
     *
     * @param plainText a string need to encrypt
     * @return Md5 string
     */
    private static string md5Enc(string plainText) {
        byte[] result = Encoding.Unicode.GetBytes(plainText);
        MD5 md5 = new MD5CryptoServiceProvider();
        byte[] output = md5.ComputeHash(result);
        return BitConverter.ToString(output).Replace("-","");  

        //char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        //try {
        //    byte[] btInput = System.Text.UTF8Encoding.UTF8.GetBytes(plainText);
        //    MessageDigest mdInst = MessageDigest.getInstance("MD5");
        //    mdInst.update(btInput);
        //    byte[] md = mdInst.digest();
        //    int j = md.length;
        //    char[] str = new char[j * 2];
        //    int k = 0;
        //    for (int i = 0; i < j; i++) {
        //        byte byte0 = md[i];
        //        str[k++] = hexDigits[byte0 >>> 4 & 0xf];
        //        str[k++] = hexDigits[byte0 & 0xf];
        //    }
        //    return new string(str);
        //} catch (Exception e) {
        //    //Log.getStackTracestring(e);
        //    return null;
        //}
    }


    public static T fromJson<T>(string json)
    {
        return JsonConvert.DeserializeObject<T>(json);
    }

    public static string toJson<T>(T t)
    {
        return JsonConvert.SerializeObject(t);
    }

    /**
     * Converts a json string to the corresponding entity class
     *
     * @param json  json string
     * @param clazz entity class
     * @param <T>   entity class name
     * @return entity
     */
    //public static <T> T fromJson(string json, Class<T> clazz) {
    //    return new Gson().fromJson(json, clazz);
    //}

    ///**
    // * @param o Converts a entity class to the corresponding  json string
    // * @return json string
    // */
    //public static string toJson(object o) {
    //    return new Gson().toJson(o);
    //}

    /**
     * compare version
     *
     * @param version1 version1
     * @param version2 version2
     * @return value =>0,version1 is more new ;value <0 ,version2 is more new
     */
    public static int compareVersion(string version1, string version2) {
        if (version1 == null || version2 == null) {
            throw new Exception("Compare version error:illegal params");
        }
        string[] versionArray1 = version1.Split("\\.".ToCharArray());
        string[] versionArray2 = version2.Split("\\.".ToCharArray());
        int index = 0;
        int minLength = versionArray1.Count() < versionArray2.Count() ? 
            versionArray1.Count(): versionArray2.Count();
        int result = 0;
        while (index < minLength && (result = versionArray1[index].Length - versionArray2[index].Length) == 0
                && (result = string.Compare(versionArray1[index],versionArray2[index])) == 0) {
            ++index;

        }
        result = (result != 0) ? result : versionArray1.Count() - versionArray2.Count();
        return result;
    }

    /**
     * Converts a comma separated elements in a string into a List
     *
     * @param list   The string that contains the comma separated elements
     * @param comma1 a external comma
     * @param comma2 a internal comma
     * @return List with the comma separated elements
     */
    public static List<string> getCommaSeparated(string list, string comma1, string comma2) {
        string[] splitStr = list.Split(comma1.ToCharArray());
        
        List<string> resultList = new List<string>();
        List<string> elementList = splitStr.ToList();
        int temp = 0;
        for (int i = 0; i < elementList.Count(); i++) {
            string element = elementList[i];
            if (!element.Contains(comma2)) {
                resultList.Add(element);
                temp++;
            } else {

                string[] splitStr2 = element.Split(comma2.ToCharArray());
                for (int j = int.Parse(splitStr2[0]); j <= int.Parse(splitStr2[1]); j++) {
                    resultList.Add(j.ToString());
                    temp++;
                }
            }
        }
        return resultList;
    }

    /**
     * Delete file in a file dir
     *
     * @param dir the path of file
     * @return boolean Returns "true" if all deletions were successful.
     * If a deletion fails, the method stops attempting to
     * delete and returns "false".
     */
    public static bool deleteDir(DirectoryInfo dir) {
        bool ret = false;
        try
        {
            ret = true;
            dir.Delete(true);
        }
        catch(Exception ex){

        }
        return ret;
        //if (dir.isDirectory()) {
        //    string[] children = dir.get
        //    for (int i = 0; i < children.length; i++) {
        //        boolean success = deleteDir(new File(dir, children[i]));
        //        if (!success) {
        //            return false;
        //        }
        //    }
        //}
        //return dir.delete();
    }


    /**
     * @param url         url for server
     * @param outFilePath output file
     */
    public static void download(string url, string outFilePath, string fileName) {
        //Uri url = new Uri("");
        
        //FileInfo file = getFilePath(outFilePath, fileName);
        //int bytesCopied = 0;
        //try {
        //    URLConnection connection = url.openConnection();
        //    int length = connection.getContentLength();
        //    FileOutputStream mOutputStream = new FileOutputStream(file);
        //    copy(connection.getInputStream(), mOutputStream);
        //    if (bytesCopied != length && length != -1) {
        //        Log.e(TAG, "Download incomplete bytesCopied=" + bytesCopied + ", length" + length);
        //    }
        //    mOutputStream.close();
        //} catch (IOException e) {
        //    Log.e(TAG, Log.getStackTracestring(e));
        //}

    }



    /**
     * decode ASCII string to standard string
     *
     * @param unicodeStr unicode string
     * @return string value
     */
    public static string decodeASCII(string unicodeStr) {
        if (unicodeStr == null) {
            return null;
        }
        StringBuilder retBuf = new StringBuilder();
        int maxLoop = unicodeStr.Length;
        char[] charTmp = unicodeStr.ToCharArray();
        for (int i = 0; i < maxLoop; i++) {
            if (charTmp[i] == '/') {
                if ((i < maxLoop - 5)
                        && ((charTmp[i + 1] == 'u') || (charTmp[i + 1] == 'U')))
                    try {
                        retBuf.Append((char) int.Parse(
                                unicodeStr.Substring(i + 2, i + 6), System.Globalization.NumberStyles.HexNumber));
                        i += 5;
                    } catch (Exception ex) {
                        retBuf.Append(charTmp[i]);
                    }
                else
                    retBuf.Append(charTmp[i]);
            } else {
                retBuf.Append(charTmp[i]);
            }
        }
        return retBuf.ToString();
    }

    /**
     * Read file by line
     *
     * @param filePathName the path name of file
     */
    private static string readFileByLines(string filePathName) {
        FileInfo file = new FileInfo(filePathName);
        StreamReader streamReader = null;
        //BufferedReader reader = null;
        StringBuilder tempstring = new StringBuilder();
        string linestring;
        try {
            streamReader = file.OpenText();
            //streamReader.Read()
            //streamReader.ReadLine()
            //reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "GB2312"));
            while ((linestring = streamReader.ReadLine()) != null) {
                tempstring.Append(linestring);
            }
        } catch (IOException e) {
            //Log.e(TAG, Log.getStackTracestring(e));
        } finally {
            if (streamReader != null) {
                try {
                    streamReader.Close();
                } catch (IOException ioe) {
                    //Log.e(TAG, Log.getStackTracestring(ioe));
                }
            }
        }
        return tempstring.ToString();
    }

    /**
     * receive inputStream to a outputStream
     *
     * @param input  inputStream
     * @param output outputStream
     */
    private static void copy(Stream input, Stream output) {
        byte[] buffer = new byte[1024 * 8];
        int n;
        
        //BufferedStream in = new BufferedStream
        
        BufferedStream inBS = new BufferedStream(input, 1024 * 8);
        BufferedStream outBS = new BufferedStream(output, 1024 * 8);
        try {
            while ((n = inBS.Read(buffer, 0, 1024 * 8)) != -1) {
                outBS.Write(buffer, 0, n);
            }
            outBS.Flush();
        } catch (IOException e) {
            //Log.e(TAG, Log.getStackTracestring(e));
        } finally {
            try {
                outBS.Close();
                inBS.Close();
            } catch (IOException e) {
                //Log.e(TAG, Log.getStackTracestring(e));
            }
        }

    }

    /**
     * According to the filePath and fileName to get the special file
     *
     * @param filePath the path of file
     * @param fileName the name of file
     * @return special file
     */
    private static FileInfo getFilePath(string filePath, string fileName) {
        DirectoryInfo dic = new DirectoryInfo(filePath);
        if (!dic.Exists) {
            dic.Create();
        }         
        return new FileInfo(filePath + "/" + fileName);
    }


    /**
     * device register
     *
     * @param context  context
     * @param protocol protocol
     * @param ver      version of protocol
     */
    public static string registerData(Context context, string protocol, string ver) {
        BestData request = buildHead(BestConstant.SERVICE_ID_REGISTER, context, BestConstant.SYSTEM_ID_COMMON);
        request.setPayloadParam(DID, generateDid(request.mid).ToLower());
        request.setPayloadParam(PROTOCOL, protocol);
        request.setPayloadParam(VER, ver);
        request.setPayloadParam(MFR, ZKTECO);
        request.setPayloadParam(DEVICE_TYPE, NO1BOSS);
        return toJson(request);


    }


    /**
     * Determine the protocol version, Checks the support list reference to determine the
     * version of the protocol to be most suitable.（The minimum value of newest version of
     * firmware and server)
     *
     * @param context  context
     * @param protocol protocol
     * @param version  version ,if version is "",means not support
     */
    public static string determineProtocol(Context context, string protocol, string version) {
        BestData request = buildHead(BestConstant.SERVICE_ID_DETERMINE_PROTOCOL, context, BestConstant.SYSTEM_ID_COMMON);
        request.setPayloadParam(PROTOCOL, protocol);
        request.setPayloadParam(VER, version);
        return toJson(request);
    }

    /**
     * login webSocket
     *
     * @param context   context
     * @param companyId password
     * @param zone      zone
     * @param dataTag   1:there have been data in device; 0:there haven't been data in device;
     */
    public static string login(Context context, string companyId, string zone, string dataTag) {
        BestData request = buildHead(BestConstant.SERVICE_ID_LOGIN, context, BestConstant.SYSTEM_ID_COMMON);
        request.setPayloadParam(COMPANY_ID, companyId);
        request.setPayloadParam(ZONE, zone);
        request.setPayloadParam(DATA_TAG, dataTag);
        return toJson(request);
    }

    /**
     * report data,this request is sent when device login successfully.
     *
     * @param context context
     *                FW_VERSION  format:company-platform-architecture-version(ZK-ANDROID-CSV2-Ver0.0.1) CSV2 : core service version 2
     * @param delay   command timeout; sid:you can set a timeout for each command(sid)
     */
    public static string report(Context context, Dictionary<string, string> delay) {
        SettingsProvider settingInstance = new SettingsProvider();
        string deviceName = (string) settingInstance.get("name", "GRAIN-V01");
        string deviceAlias = (string) settingInstance.get("alias", "IPCAM-100");
        string algorithmVersion = (string) settingInstance.get("algorithmVersion", "ROC 0.1");
        string fwVersion = (string) settingInstance.get(FW_VERSION, "0.0.0.1");
        BestData request = buildHead(BestConstant.SERVICE_ID_REPORT, context, BestConstant.SYSTEM_ID_COMMON);
        request.setPayloadParam(FW_VERSION, fwVersion);
        request.setPayloadParam("name", deviceName);
        request.setPayloadParam("alias", deviceAlias);
        request.setPayloadParam("algorithmVersion", algorithmVersion);
        request.setPayloadParam(DELAY, delay);
        return toJson(request);
    }

    /**
     * subscribe data,this request is sent when device login successfully.
     *
     * @param context context
     * @param token   "login token"
     */

    public static string subscribe(Context context, string token) {
        BestData request = buildHead(BestConstant.SERVICE_ID_SUBSCRIBE, context, BestConstant.SYSTEM_ID_BOSS);
        request.setPayloadParam(TOKEN, token);
        request.setPayloadParam(USER_SYNC_ON, "1");
        request.setPayloadParam(GROUP_SYN_CON, "1");
        return toJson(request);

    }


    /**
     * sync person snapshot
     *
     * @param context  context
     * @param snapshot person snapshot on the device
     */
    public static string uploadPersonSnapshot(Context context, string snapshot) {
        BestData request = buildHead(BestConstant.SERVICE_ID_UPLOAD_PERSONAL_SNAPSHOT, context, BestConstant.SYSTEM_ID_COMMON);
        request.setPayloadParam(SNAPSHOT, snapshot);
        return toJson(request);

    }

    /**
     * sync command snapshot
     *
     * @param context  context
     * @param snapshot command snapshot on the device
     */
    public static string uploadCommandSnapshot(Context context, string snapshot) {
        BestData request = buildHead(BestConstant.SERVICE_ID_UPLOAD_COMMAND_SNAPSHOT, context, BestConstant.SYSTEM_ID_COMMON);
        request.setPayloadParam(SNAPSHOT, snapshot);
        return toJson(request);

    }

    /**
     * Sync user information
     *
     * @param context context
     * @param start   device current snapshot
     * @param end     snapshot from server
     * @param offset  offset
     * @param size    the size of sync at one time
     */
    public static string syncData(Context context, string sid, string start, string end, string offset, string size) {
        BestData request = buildHead(sid, context, BestConstant.SYSTEM_ID_COMMON);
        request.setPayloadParam(START, start);
        request.setPayloadParam(END, end);
        request.setPayloadParam(OFFSET, offset);
        request.setPayloadParam(SIZE, size);
        return toJson(request);

    }

    /**
     * {"sid":"dms.common.device.protocol","sys":"response",
     * "sn":"329916130002","code":"00000000","message":"ok","mid":"0.9428878896854007"}
     * upload status of if received command successfully
     *
     * @param context context
     * @param sid     service id
     */

    public static string uploadReceiveResult(Context context, string sid, string code) {
        BestData request = buildHead(sid, context, BestConstant.SYSTEM_ID_RESPONSE);
        request.code = code;
        return toJson(request);
    }

    /**
     * upload Count
     *
     * @param context   context
     * @param paramList index for records index,count of faces,counting time,is a UTC time
     */
    public static string uploadCountRecord(Context context, List<Object> paramList) {
        BestData request = buildHead(BestConstant.SERVICE_ID_UPLOAD_COUNT, context, BestConstant.SYSTEM_ID_BOSS);
        request.setPayload(DATA_FORMAT, "1");
        request.setPayloadParamList(paramList);
        return toJson(request);
    }


    /**
     * upload Recognition log
     *
     * @param context   context
     * @param paramList index for records index,pid for person id,groupId for group id,recognitionTime for recognition time (UTC time)
     */
    public static string uploadRecognitionLog(Context context, List<Object> paramList) {
        BestData request = buildHead(BestConstant.SERVICE_ID_UPLOAD_RECOGNITION, context, BestConstant.SYSTEM_ID_BOSS);
        request.setPayloadParamList(paramList);
        return toJson(request);
    }


    /**
     * upload  alarm event
     *
     * @param context   context
     * @param paramList eventId for event id,eventContent event description,
     *                  alarmType for event type,condition for trigger conditions
     *                  alarmTime for event time
     */

    public static string uploadAlarmEventLog(Context context, List<Object> paramList) {
        BestData request = buildHead(BestConstant.SERVICE_ID_UPLOAD_ALARM_LOG, context, BestConstant.SYSTEM_ID_BOSS);
        request.setPayloadParamList(paramList);
        return toJson(request);
    }

    /**
     * upload Photo
     *
     * @param context   context
     * @param paramList include photo  array
     */

    public static string uploadPhoto(Context context, List<Object> paramList) {
        BestData request = buildHead(BestConstant.SERVICE_ID_UPLOAD_PHOTO, context, BestConstant.SYSTEM_ID_BOSS);
        request.setPayload(PARAM_LIST, paramList);
        return toJson(request);
    }

    /**
     * upload the device information
     *
     * @param context context
     */
    public static string uploadDeviceInformation(Context context) {
        BestData request = buildHead(BestConstant.SERVICE_ID_UPLOAD_DEVICE_INFORMATION, context, BestConstant.SYSTEM_ID_DEVICE);
        SettingsProvider settingInstance = SettingsProvider.getInstance();
        request.setPayloadParam(DATE, settingInstance.get(DATE, "123456"));
        request.setPayloadParam(LANGUAGE, settingInstance.get(LANGUAGE, "EN"));
        request.setPayloadParam(ALGORITHM_VERSION, settingInstance.get(ALGORITHM_VERSION, "0.0.5"));
        request.setPayloadParam(SYSTEM_VERSION, settingInstance.get(SYSTEM_VERSION, "0.0.5"));
        request.setPayloadParam(FIRMWARE_VERSION, settingInstance.get(FIRMWARE_VERSION, "0.0.5"));
        request.setPayloadParam(SERIAL_NUMBER, settingInstance.get(SERIAL_NUMBER, "0.0.5"));
        request.setPayloadParam(P2P_ID, settingInstance.get(P2P_ID, "0.0.5"));
        request.setPayloadParam(COMMUN_PROTOCOL_VERSION, settingInstance.get(COMMUN_PROTOCOL_VERSION, "0.0.5"));
        request.setPayloadParam(IP_ADDRESS, settingInstance.get(IP_ADDRESS, "0.0.5"));
        return toJson(request);
    }

}
}
